package larrys.pomodoro.daoimpl;

import java.util.Collection;
import java.util.Date;
import java.util.List;

import larrys.pomodoro.daoimpl.rowmappers.RoundRowMapper;
import larrys.pomodoro.domain.entity.Round;
import larrys.pomodoro.domain.repository.RoundRepository;
import larrys.pomodoro.util.AssertUtil;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import static java.sql.Types.*;

@Repository
public class RoundRepositoryImpl implements RoundRepository {

	private static Log logger = LogFactory.getLog(RoundRepositoryImpl.class);
	private JdbcTemplate jdbcTemplate;

	@Required
	@Autowired
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	private final static String SQL_QUERY_RUNNING_ROUND_BY_USERID = "SELECT * FROM round WHERE user_id = ? AND end_time is null";

	@Override
	public Round getRunningRoundByUserId(long userId) {
		AssertUtil.validId(userId);
		logger.debug("Trying to get running round by PomodoroUser[id:" + userId
				+ "]");
		Round round = null;
		try {
			List<Round> rounds = jdbcTemplate.query(
					SQL_QUERY_RUNNING_ROUND_BY_USERID, new Object[] { userId },
					new int[] { BIGINT }, new RoundRowMapper());
			if (rounds.size() > 1) {
				logger.error("Running round more then one!" + rounds);
				throw new RuntimeException("Running round more then one!"
						+ rounds);
			}
			if (rounds.size() == 1) {
				round = rounds.get(0);
			}
		} catch (RuntimeException e) {
			logger.error(
					"Exception threw while getting running round for user[id:"
							+ userId + "]", e);
			throw e;
		}
		logger.debug("Returning " + round);
		return round;
	}

	private final static String SQL_QUERY_ROUND_BY_USERID = "SELECT * FROM round WHERE user_id = ? AND start_time BETWEEN ? AND ?";

	@Override
	public Collection<Round> getRoundByUserId(long userId, Date from, Date to) {
		AssertUtil.validId(userId);
		AssertUtil.argumentNotNull(from, "from");
		AssertUtil.argumentNotNull(to, "to");
		logger.debug("Trying to get round by PomodoroUser[id:" + userId
				+ "] from " + from + " to " + to);
		Collection<Round> rounds = null;
		try {
			rounds = jdbcTemplate.query(SQL_QUERY_ROUND_BY_USERID,
					new Object[] { userId, from, to }, new int[] { BIGINT,
							TIMESTAMP, TIMESTAMP }, new RoundRowMapper());
		} catch (RuntimeException e) {
			logger.error(
					"Exception threw while getting running round for user[id:"
							+ userId + "]", e);
			throw e;
		}
		logger.debug("Returning " + rounds);
		return rounds;
	}

	private final static String SQL_QUERY_ROUND_BY_ID = "SELECT * FROM round WHERE id = ?";

	@Override
	public Round getRoundById(long id) {
		AssertUtil.validId(id);
		logger.debug("Trying to get Round[id:" + id + "]");
		try {
			Round round = jdbcTemplate.queryForObject(SQL_QUERY_ROUND_BY_ID,
					new Object[] { id }, new int[] { BIGINT },
					new RoundRowMapper());
			logger.debug("Returning " + round);
			return round;
		} catch (RuntimeException e) {
			logger.error("Exception threw while getting Round[id:" + id + "]",
					e);
			throw e;
		}
	}

	private final static String SQL_QUERY_ACTIVITY_RELATED_ROUND_BY_ACTIVITY_ID = "SELECT * FROM round WHERE activity_id = ?";

	@Override
	public Collection<Round> getActivityRelatedRound(long activityId) {
		AssertUtil.validId(activityId);
		logger.debug("Trying to get Activity[id:" + activityId
				+ "] related round");
		Collection<Round> result = jdbcTemplate.query(
				SQL_QUERY_ACTIVITY_RELATED_ROUND_BY_ACTIVITY_ID,
				new Object[] { activityId }, new int[] { BIGINT },
				new RoundRowMapper());

		logger.debug("Returning " + result);
		return result;

	}

	private final static String SQL_INSERT_ROUND = "INSERT INTO round (id, duration, start_time, end_time, interrupted, finished, comment, deleted, user_id, activity_id) VALUES(null,?,?,?,?,?,?,0,?,?)";

	@Override
	public void addRound(Round round) {
		logger.debug("Trying to add " + round);
		AssertUtil.argumentNotNull(round, "round");
		AssertUtil.validId(round.getUserId());
		AssertUtil.validId(round.getActivityId());
		try {
			jdbcTemplate.update(
					SQL_INSERT_ROUND,
					new Object[] { round.getDuration(), round.getStartTime(),
							round.getEndTime(), round.isInterrupted(),
							round.isFinished(), round.getComment(),
							round.getUserId(), round.getActivityId() },
					new int[] { INTEGER, TIMESTAMP, TIMESTAMP, TINYINT,
							TINYINT, VARCHAR, BIGINT, BIGINT });
		} catch (RuntimeException e) {
			logger.error("Exception threw while adding " + round, e);
			throw e;
		}
		logger.info(round + " added successfully!");
	}

	private final static String SQL_UPDATE_ROUND = "UPDATE round SET duration = ?, start_time = ?, end_time = ?, interrupted = ?, finished = ?, comment = ?, user_id =?, activity_id = ? WHERE id = ?";

	@Override
	public void updateRound(Round round) {
		logger.debug("Trying to update " + round);
		AssertUtil.argumentNotNull(round, "round");
		AssertUtil.propertyShouldNotBeNull(round.getStartTime(), "round",
				"startTime");
		AssertUtil.validId(round.getId());
		AssertUtil.validId(round.getUserId());
		AssertUtil.validId(round.getActivityId());
		try {
			jdbcTemplate.update(
					SQL_UPDATE_ROUND,
					new Object[] { round.getDuration(), round.getStartTime(),
							round.getEndTime(), round.isInterrupted(),
							round.isFinished(), round.getComment(),
							round.getUserId(), round.getActivityId(),
							round.getId() }, new int[] { INTEGER, TIMESTAMP,
							TIMESTAMP, TINYINT, TINYINT, VARCHAR, BIGINT,
							BIGINT, BIGINT });
		} catch (RuntimeException e) {
			logger.error("Exception threw while updating " + round, e);
			throw e;
		}
		logger.info(round + " updated successfully!");
	}

	private final static String SQL_MARK_ROUND_DELETED = "UPDATE round SET deleted = 1 WHERE id = ?";

	@Override
	public void markRoundDeletedById(long id) {
		logger.debug("Trying to mark deleted Round[id:" + id + "]");
		try {
			jdbcTemplate.update(SQL_MARK_ROUND_DELETED, new Object[] { id },
					new int[] { INTEGER });
		} catch (RuntimeException e) {
			logger.error("Exception threw while mark deleted Round[id:" + id
					+ "]", e);
			throw e;
		}
		logger.info("Round[id:" + id + "] marked deleted successfully!");
	}

	private final static String SQL_MARK_ROUND_FINISHED = "UPDATE round SET finished = 1, end_time = ? WHERE id = ?";

	@Override
	public void markRoundFinishedById(long id, Date endTime) {
		logger.debug("Trying to mark finished Round[id:" + id + "]");
		try {
			jdbcTemplate.update(SQL_MARK_ROUND_FINISHED, new Object[] {
					endTime, id }, new int[] { TIMESTAMP, BIGINT });
		} catch (RuntimeException e) {
			logger.error("Exception threw while mark finished Round[id:" + id
					+ "]", e);
			throw e;
		}
		logger.info("Round[id:" + id + "] marked finished successfully!");

	}

	private final static String SQL_MARK_ROUND_INTERRUPTED = "UPDATE round SET interrupted = 1, end_time = ? WHERE id = ?";

	@Override
	public void markRoundInterruptedById(long id, Date endTime) {
		logger.debug("Trying to mark interrupted Round[id:" + id + "]");
		try {
			jdbcTemplate.update(SQL_MARK_ROUND_INTERRUPTED, new Object[] {
					endTime, id }, new int[] { TIMESTAMP, BIGINT });
		} catch (RuntimeException e) {
			logger.error("Exception threw while mark interrupted Round[id:"
					+ id + "]", e);
			throw e;
		}
		logger.info("Round[id:" + id + "] marked interrupted successfully!");
	}

}
